package com.gitee.wsl.prism4j

internal abstract class Cloner {
    abstract fun clone(grammar: Prism4j.Grammar): Prism4j.Grammar

    abstract fun clone(token: Prism4j.Token): Prism4j.Token

    abstract fun clone(pattern: Prism4j.Pattern): Prism4j.Pattern

    internal class Impl : Cloner() {
        internal interface Context {
            fun grammar(origin: Prism4j.Grammar): Prism4j.Grammar?

            fun token(origin: Prism4j.Token): Prism4j.Token?

            fun pattern(origin: Prism4j.Pattern): Prism4j.Pattern?


            fun save(origin: Prism4j.Grammar, clone: Prism4j.Grammar)

            fun save(origin: Prism4j.Token, clone: Prism4j.Token)

            fun save(origin: Prism4j.Pattern, clone: Prism4j.Pattern)
        }

        override fun clone(grammar: Prism4j.Grammar): Prism4j.Grammar {
            return clone(ContextImpl(), grammar)
        }

        override fun clone(token: Prism4j.Token): Prism4j.Token {
            return clone(ContextImpl(), token)
        }

        override fun clone(pattern: Prism4j.Pattern): Prism4j.Pattern {
            return clone(ContextImpl(), pattern)
        }

        private fun clone(context: Context, grammar: Prism4j.Grammar): Prism4j.Grammar {
            var clone = context.grammar(grammar)
            if (clone != null) {
                return clone
            }

            val tokens = grammar.tokens()
            val out: MutableList<Prism4j.Token> = ArrayList(tokens.size)

            clone = GrammarImpl(grammar.name(), out)
            context.save(grammar, clone)

            for (token in tokens) {
                out.add(clone(context, token))
            }

            return clone
        }

        private fun clone(context: Context, token: Prism4j.Token): Prism4j.Token {
            var clone = context.token(token)
            if (clone != null) {
                return clone
            }

            val patterns = token.patterns()
            val out: MutableList<Prism4j.Pattern> = ArrayList(patterns.size)

            clone = TokenImpl(token.name(), out)
            context.save(token, clone)

            for (pattern in patterns) {
                out.add(clone(context, pattern))
            }

            return clone
        }

        private fun clone(context: Context, pattern: Prism4j.Pattern): Prism4j.Pattern {
            var clone = context.pattern(pattern)
            if (clone != null) {
                return clone
            }

            val inside = pattern.inside()

            clone = PatternImpl(
                pattern.regex(),
                pattern.lookbehind(),
                pattern.greedy(),
                pattern.alias(),
                if (inside != null) clone(context, inside) else null
            )

            context.save(pattern, clone)

            return clone
        }

        private class ContextImpl : Context {
            private val cache: MutableMap<Int?, Any?> = HashMap<Int?, Any?>(3)

            override fun grammar(origin: Prism4j.Grammar): Prism4j.Grammar? {
                return cache[key(origin)] as Prism4j.Grammar?
            }

            override fun token(origin: Prism4j.Token): Prism4j.Token? {
                return cache[key(origin)] as Prism4j.Token?
            }

            override fun pattern(origin: Prism4j.Pattern): Prism4j.Pattern? {
                return cache[key(origin)] as Prism4j.Pattern?
            }

            override fun save(origin: Prism4j.Grammar, clone: Prism4j.Grammar) {
                cache.put(key(origin), clone)
            }

            override fun save(origin: Prism4j.Token, clone: Prism4j.Token) {
                cache.put(key(origin), clone)
            }

            override fun save(origin: Prism4j.Pattern, clone: Prism4j.Pattern) {
                cache.put(key(origin), clone)
            }

            companion object {
                private fun key(o: Any): Int {
                    return System.identityHashCode(o)
                }
            }
        }
    }

    companion object {
        fun create(): Cloner {
            return Impl()
        }
    }
}
